package org.groovymud.object.registry;

import groovy.util.GroovyScriptEngine;
import groovy.util.ResourceException;
import groovy.util.ScriptException;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.codehaus.groovy.control.CompilationFailedException;
import org.groovymud.object.Container;
import org.groovymud.object.MudObject;
import org.groovymud.object.ObjectLocation;
import org.groovymud.object.alive.Player;
import org.groovymud.object.registry.domain.DomainManager;
import org.groovymud.object.registry.persistence.DataSourceException;
import org.groovymud.object.registry.persistence.MudDataSource;
import org.springframework.beans.factory.annotation.Autowired;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;

/* Copyright 2008 Matthew Corby-Eaglen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
/**
 * The MudObjectAttendant is responsible for loading, saving, registering and
 * configuring all mud objects in the game.
 * 
 * The object can have an id
 * 
 */
public class MudObjectAttendant {

	private static final String PLAYER_STORE = new String("playerfiles" + File.separator);

	private ObjectLocation theVoid;

	private ObjectLocation playerImpl;

	private static final Logger logger = Logger.getLogger(MudObjectAttendant.class);

	private transient MudDataSource<MudObject> persistence;

	private transient GroovyScriptEngine groovyScriptEngine;

	private transient XStream xstream;

	private transient List<DomainManager<MudObject>> domainManagers;
	private final Registry objectRegistry;

	@Autowired(required = true)
	public MudObjectAttendant(Registry reg) {
		this.objectRegistry = reg;
	}

	/** 
	 * gets a list of all object handles registered by any domain managers
	 * @return
	 */
	public List<String> getObjectHandles() {
		List<String> handles = new ArrayList<String>();
		for (DomainManager<MudObject> domainManager : domainManagers) {
			handles.addAll(domainManager.getHandles());
		}
		return handles;
	}

	public MudObject cloneObject(String handle) throws MudCloneException {
		MudObject o = getObjectRegistry().getMudObject(handle);
		if (o == null) {
			o = loadObject(handle);
		}
		o = cloneObject(o, true);
		getObjectRegistry().register(o, handle);
		return o;
	}

	public MudObject findOrClone(ObjectLocation loc) throws MudCloneException {
		MudObject obj = getObjectRegistry().getMudObject(loc.getHandle());
		if (obj == null) {
			load(loc);
			obj = cloneObject(loc.getHandle());
		}
		return obj;
	}

	protected MudObject cloneObject(MudObject o, boolean register) throws MudCloneException {
		if (o == null) {
			throw new IllegalArgumentException("object cannot be null");
		}
		Exception anException = null;
		MudObject copy = null;
		try {
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			persistence.serialize(o, out);
			InputStream in = new ByteArrayInputStream(out.toByteArray());
			copy = persistence.deserializeObject(in);

		} catch (XStreamException e) {
			anException = e;
			logger.error(e, e);
		} finally {
			if (anException != null) {
				throw new MudCloneException("could not clone object", anException);
			} else if (register) {
				getObjectRegistry().register(copy);
			}
		}

		return copy;
	}

	/**
	 * loads a mudobject object and initialise it using a handle
	 * 
	 * Each domain manager is checked for an instance of the required handle
	 * 
	 * an instance of that object is loaded from the manager, which may or may not
	 * be a single instance
	 * 
	 * @param ObjectLocation
	 *            location - representing where the object can be found in the
	 *            spring container
	 * @throws MudCloneException 
	 */
	private MudObject loadObject(String handle) throws MudCloneException {
		MudObject obj = null;
		for (DomainManager<MudObject> domainManager : domainManagers) {

			if (domainManager.contains(handle)) {
				obj = domainManager.getObject(handle);
				break;
			}
		}
		return obj;
	}

	public void load(ObjectLocation location) {
		for (DomainManager<MudObject> domainManager : domainManagers) {
			if (domainManager.contains(location.getHandle())) {
				break;
			} else if (domainManager.canLoad(location.getDefinition())) {
				logger.info("loading definition" + location.getDefinition());
				domainManager.loadDomain(location.getDefinition());
			}
		}
	}

	public Player loadPlayerData(String username) throws CompilationFailedException, ResourceException, ScriptException, DataSourceException {
		logger.info("loading playerImpl object..");
		Player player = null;
		synchronized (this.getClass()) {
			load(getPlayerImpl());
			player = (Player) persistence.loadObject(PLAYER_STORE + username);
		}
		return player;
	}

	public void moveToLocation(MudObject player) throws InstantiationException, FileNotFoundException, CompilationFailedException, MudCloneException {
		Container room = null;
		logger.info("moving player to location.");
		ObjectLocation location = player.getContainerLocation();
		room = (Container) getObjectRegistry().getMudObject(location.getHandle());
		if (room == null) {
			logger.info("loading container..");
			load(location);
			room = (Container) cloneObject(location.getHandle());
		}

		if (room == null) {
			throw new InstantiationException("container was null!");
		}
		logger.info("adding player to container");
		room.addMudObject(player);
	}

	public void movePlayerToVoid(Player player) throws IOException, FileNotFoundException {
		try {
			logger.info("moving player to the void..");
			player.setContainerLocation(getTheVoid());
			moveToLocation(player);
		} catch (CompilationFailedException e) {
			logger.error(e, e);
		} catch (InstantiationException e) {
			logger.error(e, e);
		} catch (MudCloneException e) {
			logger.error(e, e);
		}
	}

	public Player createNewPlayer(String username) throws MudCloneException {
		logger.info("creating new player");
		MudObject obj = loadObject(getPlayerImpl().getHandle());
		Player player = (Player) cloneObject(obj, false);
		String upperName = username.substring(0, 1).toUpperCase() + username.substring(1);

		player.setName(upperName);
		player.addShortName(upperName);
		player.addShortName(username);

		return player;
	}

	public void savePlayerData(Player player) {
		logger.info("saving player..");

		try {
			persistence.saveObject(PLAYER_STORE + player.getPlayerCredentials().getUsername(), player);

		} catch (Exception e) {
			try {
				player.getTerminalOutput().writeln(e.getMessage());
			} catch (IOException e1) {
				logger.error(e1, e1);
			}

			logger.error(e, e);
		}
	}

	public ObjectLocation getTheVoid() {
		return theVoid;
	}

	public void setTheVoid(ObjectLocation voidLocation) {
		this.theVoid = voidLocation;
	}

	public ObjectLocation getPlayerImpl() {
		return playerImpl;
	}

	public void setPlayerImpl(ObjectLocation playerLoc) {
		this.playerImpl = playerLoc;
	}

	public GroovyScriptEngine getGroovyScriptEngine() {
		return groovyScriptEngine;
	}

	public void setGroovyScriptEngine(GroovyScriptEngine gse) {
		this.groovyScriptEngine = gse;
	}

	public Registry getObjectRegistry() {
		return objectRegistry;
	}

	public MudDataSource<MudObject> getPersistence() {
		return persistence;
	}

	public void setPersistence(MudDataSource<MudObject> datasource) {
		this.persistence = datasource;
	}

	/**
	 * @return the domainManagers
	 */
	public List<DomainManager<MudObject>> getDomainManagers() {
		return domainManagers;
	}

	/**
	 * @param domainManagers the domainManagers to set
	 */
	public void setDomainManagers(List<DomainManager<MudObject>> domainManagers) {
		this.domainManagers = domainManagers;
	}

	/**
	 * @return the xstream
	 */
	public XStream getXstream() {
		return xstream;
	}

	/**
	 * @param xstream the xstream to set
	 */
	public void setXstream(XStream xstream) {
		this.xstream = xstream;
	}
}
